package beer.coder.walawala.gui.handler.item;

import beer.coder.walawala.common.config.ItemConfigure;
import beer.coder.walawala.common.constant.ItemType;
import beer.coder.walawala.common.logger.Log;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.jar.JarFile;
import javafx.application.Application;

/**
 * TODO
 *
 * @author amath0312
 * @version 1.0
 * @date 2019-10-27 20:18
 */
public class ItemActionHandler {

  private static final String TAG = ItemActionHandler.class.getName();
  private static final ItemActionHandler instance = new ItemActionHandler();

  public static ItemActionHandler getInstance() {
    return instance;
  }

  private ItemActionHandler() {
    try {
      loadByAnnotation();
    } catch (IOException e) {
      //ignore
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    ItemActionHandler.getInstance();
  }

  private Map<ItemType, BiConsumer<Application, ItemConfigure>> handlers = new ConcurrentHashMap<>();

  public void regist(ItemType itemType, BiConsumer<Application, ItemConfigure> handler) {
    handlers.put(itemType, handler);
  }

  public BiConsumer<Application, ItemConfigure> unregist(ItemType itemType) {
    if (handlers.containsKey(itemType)) {
      return handlers.remove(itemType);
    } else {
      return null;
    }
  }

  public void action(Application app, ItemConfigure itemConfigure) {
    BiConsumer<Application, ItemConfigure> handler;
    if (handlers.containsKey(itemConfigure.getItemType())) {
      handler = handlers.get(itemConfigure.getItemType());
    } else {
      handler = handlers.get(ItemType.EXE);
    }
    Annotation an = null;

    if (handler != null) {
      handler.accept(app, itemConfigure);
    }
  }

  private void loadByAnnotation() throws IOException {
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    String packageName = this.getClass().getPackage().getName();
    String packagePath = packageName.replace('.', '/');
    Enumeration<URL> resources = loader.getResources(packagePath);

    List<Class> classes = new LinkedList<>();

    while (resources.hasMoreElements()) {
      URL resource = resources.nextElement();
      if (resource.getProtocol().equalsIgnoreCase("jar")) {
        classes.addAll(loadFromJar(resource, packageName, packagePath));
      } else {
        classes.addAll(loadFromFolder(resource, packageName, packagePath));
      }
    }

    classes.forEach(cls -> {
      ItemAction itemAction = (ItemAction) cls.getAnnotation(ItemAction.class);
      if (itemAction != null) {
        Arrays.stream(itemAction.value()).forEach(itemType -> {
          try {
            Log.d(TAG, "[ItemActionhandler - Line 101] regist " + cls.getName() + " for " + itemType);
            this.regist(itemType, (BiConsumer<Application, ItemConfigure>) cls.newInstance());
          } catch (InstantiationException e) {
            e.printStackTrace();
          } catch (IllegalAccessException e) {
            e.printStackTrace();
          }
        });
      }
    });
  }

  private List<Class> loadFromJar(URL resource, String packageName, String packagePath) throws IOException {
    List<Class> classes = new LinkedList<>();

    JarURLConnection conn = (JarURLConnection) resource.openConnection();
    JarFile jar = conn.getJarFile();

    jar.stream().filter(
        entry -> entry.getName().startsWith(packagePath) && entry.getName().endsWith(".class") && !entry.getName()
            .contains("$"))
        .forEach(entry -> {
          try {
            String className = entry.getName().replace('/', '.');
            className = className.substring(0, className.length() - ".class".length());
            classes.add(Class.forName(className));
          } catch (ClassNotFoundException e) {
            e.printStackTrace();
          }
        });
    return classes;
  }

  private List<Class> loadFromFolder(URL resource, String packageName, String packagePath) {
    List<Class> classes = new LinkedList<>();

    File file = new File(resource.getPath());

    Arrays.stream(file.listFiles()).forEach(child -> {
      if (child.getName().endsWith(".class")) {
        String className = packageName + "." + child.getName();
        className = className.substring(0, className.length() - ".class".length());
        try {
          classes.add(Class.forName(className));
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
      } else if (child.isDirectory()) {
        try {
          classes.addAll(
              loadFromFolder(child.toURI().toURL(), packageName + "." + child.getName(), child.getPath())
          );
        } catch (MalformedURLException e) {
          e.printStackTrace();
        }
      }
    });

    return classes;
  }
}
